﻿using Devil;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
using static UnityEditor.UIElements.ToolbarMenu;

namespace GameToolkit.Editor
{
    class BuildAssetReporter : IPreprocessBuildWithReport, IPostprocessBuildWithReport
    {
        //readonly string RELEASE_PACKAGE_SYMBOL = "RELEASE_PACKAGE";
        public int callbackOrder => 0;

        public void OnPreprocessBuild(BuildReport report)
        {
            AssetsUtil.mIsBuildingAsset = true;
            //Debug.Log($"Start Build Asset for {report.summary.platform}");
            //string[] symbols;
            //PlayerSettings.GetScriptingDefineSymbolsForGroup(report.summary.platformGroup, out symbols);
            //var len = symbols == null ? 0 : symbols.Length;
            //if (len == 0 || !symbols.Contains(RELEASE_PACKAGE_SYMBOL))
            //{
            //    var newsymbols = new string[len + 1];
            //    if (len > 0)
            //        System.Array.Copy(symbols, newsymbols, len);
            //    newsymbols[len] = RELEASE_PACKAGE_SYMBOL;
            //    PlayerSettings.SetScriptingDefineSymbolsForGroup(report.summary.platformGroup, newsymbols);
            //    Debug.Log($"Add symbol: {RELEASE_PACKAGE_SYMBOL}");
            //}
        }

        public void OnPostprocessBuild(BuildReport report)
        {
            AssetsUtil.mIsBuildingAsset = false;
            //string[] symbols;
            //PlayerSettings.GetScriptingDefineSymbolsForGroup(report.summary.platformGroup, out symbols);
            //var len = symbols == null ? 0 : symbols.Length;
            //if (len > 0)
            //{
            //    var index = System.Array.IndexOf(symbols, RELEASE_PACKAGE_SYMBOL);
            //    if (index != -1)
            //    {
            //        var newsymbols = new string[len - 1];
            //        if (index > 0)
            //            System.Array.Copy(symbols, newsymbols, index);
            //        if (index < len - 1)
            //            System.Array.Copy(symbols, index + 1, newsymbols, index, len - index - 1);
            //        PlayerSettings.SetScriptingDefineSymbolsForGroup(report.summary.platformGroup, newsymbols);
            //        Debug.Log($"Remove symbol: {RELEASE_PACKAGE_SYMBOL}");
            //    }
            //}
        }

    }

    public partial class MakeABUtil //: EditorCanvasWindow
    {

        static readonly Regex VALID_AB_NAME = new Regex(@"^[\.a-z0-9_/\[\]\(\)]+$", RegexOptions.IgnoreCase);

        static bool AssertAbName(string abname)
        {
            var valid = VALID_AB_NAME.IsMatch(abname);
            Debug.AssertFormat(valid, "AB命名 ({0}) 错误;\n AB资源或文件夹只能包含\".a-z0-9_/[]()\"这些字符(不区分大小写)。", abname);
            return valid;
        }

        public class ABData
        {
            public readonly int id;
            public readonly string abName;
            public readonly HashSet<string> assets;
            // public bool isScene;

            public ABData(int id, string abname)
            {
                this.id = id;
                this.abName = abname;
                assets = new HashSet<string>();
            }
        }

        [System.Flags]
        public enum EAssetFilter
        {
            AnimationClip = 1 << 0,
            AudioClip = 1 << 1,
            AudioMixer = 1 << 2,
            ComputeShader = 1 << 3,
            Font = 1 << 4,
            Material = 1 << 5,
            Mesh = 1 << 6,
            Model = 1 << 7,
            PhysicsMaterial = 1 << 8,
            Prefab = 1 << 9,
            Scene = 1 << 10,
            ScriptableObject = 1 << 11,
            Shader = 1 << 12,
            ShaderVariantCollection = 1 << 13,
            Spriteatlas = 1 << 14,
            TextAsset = 1 << 15,
            Texture = 1 << 16,
            VideoClip = 1 << 17,
            AnimatorController = 1 << 18,
            TerrainData = 1 << 19,
        }

        const EAssetFilter DEFAULT_ASSET_FILTER =
            EAssetFilter.AudioClip
            | EAssetFilter.AudioMixer
            | EAssetFilter.ComputeShader
            | EAssetFilter.Font
            | EAssetFilter.Material
            | EAssetFilter.PhysicsMaterial
            | EAssetFilter.Prefab
            | EAssetFilter.Scene
            | EAssetFilter.ScriptableObject
            | EAssetFilter.Shader
            | EAssetFilter.ShaderVariantCollection
            | EAssetFilter.Spriteatlas
            | EAssetFilter.TextAsset
            | EAssetFilter.Texture
            | EAssetFilter.AnimatorController;
        //"t:prefab t:spriteatlas t:scriptableobject t:scene t:audioclip t:material t:shader t:texture";
        public static BuildTarget GetCurrentPlatform()
        {
            switch (Application.platform)
            {
                case RuntimePlatform.WindowsEditor:
                case RuntimePlatform.WindowsPlayer:
                    if (System.IntPtr.Size == 8)
                        return BuildTarget.StandaloneWindows64;
                    else
                        return BuildTarget.StandaloneWindows;
                default:
                    return BuildTarget.NoTarget;
            }
        }

        [InitializeOnLoadMethod]
        static void OnInit()
        {
            //EditorApplication.playModeStateChanged -= OnPlayStateChanged;
            //EditorApplication.playModeStateChanged += OnPlayStateChanged;
            if (/*EditorApplication.isPlayingOrWillChangePlaymode && */EditorPrefs.GetBool("MakeAB.assetsUtil", true))
            {
                InnerAssetsUtil.InnerBundle = new ABMakeInfo();
            }
        }

        //private static void OnPlayStateChanged(PlayModeStateChange stat)
        //{
        //    if (stat == PlayModeStateChange.EnteredPlayMode && EditorPrefs.GetBool("MakeAB.assetsUtil", false))
        //    {
        //        InnerAssetsUtil.InnerBundle = new ABMakeInfo();
        //    }
        //}
        //static string inAb = "";
        //static string dependentAb = "";
        //static string notdepdentAb = "";
        //static bool detailSearchMode;

       
        static bool IsMatchAb(string ab, Regex matchPattern, Regex excludePattern)
        {
            if (matchPattern != null && !matchPattern.IsMatch(ab))
                return false;
            if (excludePattern != null && excludePattern.IsMatch(ab))
                return false;
            return true;
        }

        // 查找资源依赖链条
        static void FindDependencyAssets(MakeABConfig config, string ab, string dependentPattern, string notdependentPattern, string output, bool findAsset, bool detail)
        {
            if (output != null && output.Length > 1)
            {
                var c = output[output.Length - 1];
                if (c == '/' || c == '\\')
                    output = output.Substring(0, output.Length - 1);
            }
            var match = string.IsNullOrEmpty(dependentPattern) ? null : new Regex(dependentPattern, RegexOptions.IgnoreCase);
            var notmatch = string.IsNullOrEmpty(notdependentPattern) ? null : new Regex(notdependentPattern, RegexOptions.IgnoreCase);
            Debug.Assert(!string.IsNullOrEmpty(ab), "请指定需要查找的 ab 名字");
            var title = "搜索资源依赖";
            Dictionary<int, ABData> abmap = null;
            //var findAsset =  EditorUtility.DisplayDialog(title, "是否执行资源查找", "始终查找", "按需查找");
            var makenode = MakeAB.ParseAssetsTree(true);
            if (findAsset || !ReadAbmap(ref abmap))
            {
                if (makenode != null)
                {
                    abmap = ParseABmap(config, makenode, true, true);
                    SaveAbmap(abmap);
                }
            }
            if (abmap == null || makenode == null)
                return;
            var str = string.Format("开始查找 \"{0}\" 中的资源在 \"{1}\" 中的依赖资源", ab,
                string.IsNullOrEmpty(dependentPattern) ? "其他AB" : dependentPattern);
            Debug.Log(str);
            bool cancel = false;
            var buffer = string.IsNullOrEmpty(output) ? null : new StringBuilder();
            HashSet<int> dependents = new HashSet<int>();
            Stack<DependencyAsset> dependencyStack = new Stack<DependencyAsset>();
            foreach (var data in abmap.Values)
            {
                if (data.assets == null)
                    continue;
                var num = data.assets.Count;
                if (num == 0 || !Regex.IsMatch(data.abName, ab, RegexOptions.IgnoreCase))
                    continue;
                var f = 1f / num;
                var n = 0;
                title = string.Format("Assets in \"{0}\"", data.abName);
                if (buffer != null)
                {
                    buffer.Clear();
                    buffer.Append("Assets In \"").Append(data.abName).Append("\"\n\n");
                }
                foreach (var asset in data.assets)
                {
                    cancel = EditorUtility.DisplayCancelableProgressBar(title, asset, n++ * f);
                    if (cancel)
                        break;
                    // +++++++++++++++++
                    if (detail)
                    {
                        dependents.Clear();
                        dependencyStack.Clear();
                        var it = new DependencyAsset(asset, null);
                        dependents.Add(asset.GetHashCode());
                        dependencyStack.Push(it);
                        while (dependencyStack.Count > 0)
                        {
                            it = dependencyStack.Pop();
                            var deps = AssetDatabase.GetDependencies(it.path, false);
                            foreach (var dependentOn in deps)
                            {
                                var next = new DependencyAsset(dependentOn, null);
                                if (dependents.Add(next.path.GetHashCode()))
                                {
                                    var node = MakeABNode.FindNode(makenode, next.path);
                                    var toab = node == null || node.asset == null ? null : node.asset.GetABName(makenode, node.folder, next.path);
                                    next.desc = string.IsNullOrEmpty(it.desc) ? ParallelUtils.Concat('\"', dependentOn, '\"')
                                                    : ParallelUtils.Concat('\"', dependentOn, "\"\n\t\t <- ", it.desc);
                                    if (string.IsNullOrEmpty(toab) || !IsMatchAb(toab, match, notmatch))
                                    {
                                        dependencyStack.Push(next);
                                    }
                                    else
                                    {
                                        str = string.Format("\"{0}\" in \"{1}\" \n\tdependent {2} \n\t in \"{3}\"", asset, data.abName, next.desc, toab);
                                        //Debug.Log(str);
                                        if (buffer != null)
                                            buffer.Append(str).NextLine().NextLine();
                                    }
                                }
                            }
                        }
                    }
                    // +++++++++++++++++
                    else
                    {
                        var dependencies = AssetDatabase.GetDependencies(asset);
                        foreach (var depedentOn in dependencies)
                        {
                            var node = MakeABNode.FindNode(makenode, depedentOn);
                            var toab = node == null || node.asset == null ? null : node.asset.GetABName(makenode, node.folder, depedentOn);
                            if (!string.IsNullOrEmpty(toab) && IsMatchAb(toab, match, notmatch))
                            {
                                str = string.Format("\"{0}\" in \"{1}\" \n\tdependent \"{2}\" in \"{3}\"", asset, data.abName, depedentOn, toab);
                                //Debug.Log(str);
                                if (buffer != null)
                                    buffer.Append(str).NextLine().NextLine();
                            }
                        }
                    }
                }
                if (buffer != null)
                {
                    var outfile = ParallelUtils.Concat(output, '/', Regex.Replace(data.abName, @"(/|\\)", "_"), ".log");
                    ParallelUtils.Schedule(new LogCmd(outfile, buffer.ToString()), false);
                    Debug.LogFormat("Output file: {0}", outfile);
                }
                if (cancel)
                    break;
            }
            EditorUtility.ClearProgressBar();
        }

        struct DependencyAsset
        {
            public string path;
            public string desc;
            public DependencyAsset(string path, string prefix)
            {
                this.path = path;
                this.desc = prefix;
            }
            public override int GetHashCode()
            {
                return path.GetHashCode();
            }
        }

        class LogCmd : ICommand
        {
            public string file;
            public string content;
            public LogCmd(string file, string content)
            {
                this.file = file;
                this.content = content;
            }

            public void Execute()
            {
                File.WriteAllText(file, content, Encoding.UTF8);
            }
        }

        static string GetAssetFilter(MakeABConfig config)
        {
            var assetfilter = config.m_AssetFilter;// (EAssetFilter)EditorPrefs.GetInt("MakeAB.filter", (int)DEFAULT_ASSET_FILTER);
            var buf = ParallelUtils.GetBuilder();
            var enumValues = System.Enum.GetValues(typeof(EAssetFilter));
            for (int i = 0; i < enumValues.Length; i++)
            {
                var v = (EAssetFilter)enumValues.GetValue(i);
                if ((assetfilter & v) != 0)
                {
                    buf.Append("t:").Append(v.ToString()).Append(" ");
                }
            }
            return ParallelUtils.ReturnRelease(buf);
        }

        static Dictionary<int, ABData> ParseABmap(MakeABConfig config, MakeABNode makenode, bool selectDependency, bool displayProcessbar)
        {
            var searchFolders = new List<string>();
            if (makenode.children != null)
            {
                foreach (var node in makenode.children)
                {
                    searchFolders.Add(node.folder);
                }
            }

            if (searchFolders.Count == 0)
                return null;

            var cancel = displayProcessbar && EditorUtility.DisplayCancelableProgressBar("Find Assets", searchFolders[0], 0.1f);
            if (cancel)
            {
                if (displayProcessbar)
                    EditorUtility.ClearProgressBar();
                return null;
            }
            Dictionary<int, ABData> abmap = new Dictionary<int, ABData>();
            HashSet<string> allAssets = new HashSet<string>();
            var assets = AssetDatabase.FindAssets(GetAssetFilter(config), searchFolders.ToArray());
            var f = assets == null || assets.Length == 0 ? 1f : 1f / assets.Length;
            var len = 0;
            foreach (var guid in assets)
            {
                len++;
                var path = AssetDatabase.GUIDToAssetPath(guid);
                if (makenode.makeAbAssets.Contains(path))
                    continue;
                var node = MakeABNode.FindNode(makenode, path);
                if (node == null || node.asset == null || node.path == path)
                {
                    // Debug.LogErrorFormat("skip assets: {0}", path);
                    continue;
                }
                if (!node.asset.IsIncludeAsset(node.folder, path, null))
                    continue;
                if (!allAssets.Add(path))
                    continue;
                cancel = displayProcessbar && EditorUtility.DisplayCancelableProgressBar("Processing Assets", path, len * f);
                if (cancel)
                {
                    if (displayProcessbar)
                        EditorUtility.ClearProgressBar();
                    return null;
                }
                ABData ab;
                var abname = node.asset.GetABName(makenode, node.folder, path);
                var abid = ParallelUtils.IgnoreCaseToHash(abname);
                var exist = abmap.TryGetValue(abid, out ab);
                var isScene = path.EndsWithIgnoreCase(".scene");
                Debug.AssertFormat(!exist || !isScene, "场景AB(\"{0}\") 必须定义为唯一资源", abname);
                if (!exist)
                {
                    ab = new ABData(abid, abname);
                    abmap[abid] = ab;
                }
                ab.assets.Add(path);
            }
            var parse = !selectDependency || AppendDependency(config, makenode, abmap, allAssets, false);
            if (!parse)
                abmap.Clear();
            if (displayProcessbar)
                EditorUtility.ClearProgressBar();
            return abmap;
        }

        static bool AppendDependency(MakeABConfig config, MakeABNode makenode, Dictionary<int, ABData> abmap, HashSet<string> allassets, bool clearProcessBar)
        {
            var f = allassets.Count > 0 ? 1f / allassets.Count : 1f;
            var num = 0;
            foreach (var path in allassets)
            {
                var cancel = EditorUtility.DisplayCancelableProgressBar("Find Dependent Assets", path, num++ * f);
                if (cancel)
                {
                    EditorUtility.ClearProgressBar();
                    return false;
                }

                var depedents = AssetDatabase.GetDependencies(path, true);
                foreach (var dependent in depedents)
                {
                    if (dependent.EndsWithIgnoreCase(".cs") || !File.Exists(dependent))
                        continue;
                    var node = MakeABNode.FindNode(makenode, dependent);
                    if (node == null || node.asset == null || node.path == dependent)
                        ///*node.asset.m_PackageMode != MakeAB.EPackMode.Dependent ||*/ !node.asset.IsIncludeAsset(node.folder, dependent))
                        continue;
                    //var pack = node.asset.GetPackType(dependent);
                    //if (pack == MakeAB.EPackMode.Scene)
                    //    continue;
                    if (!node.asset.IsIncludeAsset(node.folder, dependent, path))
                        continue;

                    ABData ab;
                    var abname = node.asset.GetABName(makenode, node.folder, dependent);
                    var abid = ParallelUtils.IgnoreCaseToHash(abname);
                    if (!abmap.TryGetValue(abid, out ab))
                    {
                        ab = new ABData(abid, abname);
                        abmap[abid] = ab;
                    }
                    ab.assets.Add(dependent);
                }
            }
            if (clearProcessBar)
                EditorUtility.ClearProgressBar();
            return true;
        }

        static void SaveAbmap(Dictionary<int, ABData> abmap)
        {
            if (abmap == null)
                return;
            var buf = ParallelUtils.GetBuilder();
            buf.Append(System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ddd\n\n"));
            foreach (var ab in abmap.Values)
            {
                buf.Append('@').Append(ab.abName).Append('\n');
                foreach (var path in ab.assets)
                {
                    buf.Append(path).Append('\n');
                }
                buf.Append('\n');

            }
            File.WriteAllText("Assets/AssetBundleInfo.txt", ParallelUtils.ReturnRelease(buf));
        }

        static bool ReadAbmap(ref Dictionary<int, ABData> abmap)
        {
            if (!File.Exists("Assets/AssetBundleInfo.txt"))
                return false;
            if (abmap == null)
                abmap = new Dictionary<int, ABData>();
            var file = File.ReadAllText("Assets/AssetBundleInfo.txt");
            var reader = new SitcomFile();
            reader.Load(file);
            reader.BeginRead();
            while (reader.NextCmd())
            {
                if (reader.NextKeywords())
                {
                    var abname = reader.keyword.text;
                    var isscene = abname.EndsWithIgnoreCase(".scene");
                    var assets = reader.NextContent() ? reader.keyword.text : "";
                    var abid = ParallelUtils.IgnoreCaseToHash(abname);
                    ABData data = new ABData(abid, abname);
                    foreach (var asset in assets.Split('\n'))
                    {
                        var path = asset.Trim();
                        if (!string.IsNullOrEmpty(path))
                            data.assets.Add(path);
                    }
                    abmap[abid] = data;
                }
            }
            return true;
        }

        [MenuItem("Game Toolkit/AssetBundle/搜索 AssetBundle 资源(多线程)")]
        static void FindAssetBundlesJobs()
        {
            var cmd = new SearchAssetsForBuild(true, ProjectConfiguration.GetConfigurable<MakeABConfig>());
            var search = ParallelUtils.Schedule(cmd, true);
            ParallelUtils.Schedule(() => EditorUtility.ClearProgressBar(), true, search);
        }

        [MenuItem("Game Toolkit/AssetBundle/(搜索)打包 AssetBundle")]
        static void BuildAssetBundles()
        {
            BuildAssetBundles(true);
        }

        [MenuItem("Game Toolkit/AssetBundle/打包 AssetBundle")]
        static void BuildAssetBundlesFromCache()
        {
            BuildAssetBundles(false);
        }

        [MenuItem("Game Toolkit/AssetBundle/分析 AssetBundle 依赖")]
        static void BuildAssetBundlesTree()
        {
            var cmd = new BuildABTree();
            ParallelUtils.Schedule(cmd, true);
        }

        [MenuItem("Game Toolkit/AssetBundle/测试打包代码")]
        static void TestBuildAB()
        {
            AssetBundleBuild build;
            build.assetBundleName = "test";
            build.assetBundleVariant = "";
            var txt = AssetDatabase.FindAssets("t:textasset", new string[] { "Assets" });
            if(txt.Length > 0)
            {
                build.addressableNames = new string[] { AssetDatabase.GUIDToAssetPath(txt[0]) };
                build.assetNames = build.addressableNames;
            }
            else
            {
                build.addressableNames = new string[0];
                build.assetNames = new string[0];
            }
            List<AssetBundleBuild> abs = new List<AssetBundleBuild> { build };
            var config = ProjectConfiguration.GetConfigurable<MakeABConfig>();
            var platform = config.m_BuildTarget;// (BuildTarget)EditorPrefs.GetInt("MakeAB.platform", (int)GetCurrentPlatform());
            if (platform == BuildTarget.NoTarget)
                platform = GetCurrentPlatform();
            var dest = NativeFileSystem.Combine(Application.dataPath, "../TestBuildScript");
            NativeFileSystem.EnsureDirectory(dest);
            BuildPipeline.BuildAssetBundles(dest, abs.ToArray(), 0, platform);
        }

        [MenuItem("Game Toolkit/AssetsBundle/打包场景", true)]
        static bool CanBuildScene()
        {
            var assets = Selection.objects;
            for (int i = 0; i < assets.Length; i++)
            {
                if (!(assets[i] is SceneAsset))
                    return false;
            }
            return assets.Length > 0;
        }

        [MenuItem("Game Toolkit/AssetBundle/打包场景", priority = 2000)]
        static void BuildScene()
        {
            var config = ProjectConfiguration.GetConfigurable<MakeABConfig>();
            var scene = Selection.activeObject;
            BuildTarget platform = config.m_BuildTarget;
            // (BuildTarget)EditorPrefs.GetInt("MakeAB.platform", (int)GetCurrentPlatform());
            if (config.m_BuildTarget == BuildTarget.NoTarget)
                platform = GetCurrentPlatform();
            var dest = config.m_Destination;
            //EditorPrefs.GetString("MakeAB.output", "Assets/StreamingAssets");
            // Path.Combine(Application.dataPath, "StreamingAssets", targetPlatform.ToString());
            if (!dest.EndsWithIgnoreCase(platform.ToString()))
                dest = NativeFileSystem.Combine(dest, platform.ToString());
            dest = NativeFileSystem.Combine(dest, "scenes");
            var option = config.m_BuildOption;// (BuildAssetBundleOptions)EditorPrefs.GetInt("MakeAB.option", 0);
                                              //#if UNITY_2020_1_OR_NEWER
                                              //#else

#if CHINA_VERSION
#if ENCRYPT_ENABLED
            option |= BuildAssetBundleOptions.ChunkBasedCompression | BuildAssetBundleOptions.EnableProtection;
            option &= ~BuildAssetBundleOptions.UncompressedAssetBundle;
#else
            option &= ~BuildAssetBundleOptions.EnableProtection;
#endif

            var enopt = BuildAssetBundleOptions.EnableProtection | BuildAssetBundleOptions.ChunkBasedCompression;
            var encript = (option & enopt) == enopt && !string.IsNullOrEmpty(MakeAB.DEFAULT_KEY);
            if (encript)
            {
                BuildPipeline.SetAssetBundleEncryptKey(MakeAB.DEFAULT_KEY);
            }
            else
            {
                option &= ~BuildAssetBundleOptions.EnableProtection;
                BuildPipeline.SetAssetBundleEncryptKey(null);
            }
#endif
            var lst = new List<AssetBundleBuild>();
            foreach (var obj in Selection.objects)
            {
                var s = obj as SceneAsset;
                AssetBundleBuild ab;
                ab.assetBundleName = string.Format("{0}.scene", s.name);
                ab.assetNames = new string[] { AssetDatabase.GetAssetPath(s) };
                ab.assetBundleVariant = "";
                ab.addressableNames = null;
                lst.Add(ab);
            }
            BuildPipeline.BuildAssetBundles(dest, lst.ToArray(), option, platform);
        }


        [MenuItem("Game Toolkit/AssetBundle/打包可执行程序", priority = 2000)]
        static void BuildPlayer()
        {
            BuildPlayer(false, false);
        }

        [MenuItem("Game Toolkit/AssetBundle/打包可执行程序(Debug)", priority = 2000)]
        static void BuildDebugPlayer()
        {
            BuildPlayer(true, false);
        }

        [MenuItem("Game Toolkit/AssetBundle/打包可执行程序(ScriptOnly)", priority = 2000)]
        static void BuildDebugScript()
        {
            BuildPlayer(true, true);
        }

        [MenuItem("Game Toolkit/AssetBundle/检查资源 ID 冲突")]
        static void CheckAssetConflicts()
        {
            var cmd = new CheckAssetConflict();
            ParallelUtils.Schedule(cmd, false);
        }

        static void BuildPlayer(bool development, bool scriptOnly)
        {
            var def = EditorPrefs.GetString("Build.dest", Application.dataPath);
            var output = EditorUtility.SaveFolderPanel("Build Player", NativeFileSystem.GetFolderName(def), NativeFileSystem.GetFileName(def, false));
            if (string.IsNullOrEmpty(output))
                return;

            var abnode = MakeAB.ParseAssetsTree(true);
            var config = ProjectConfiguration.GetConfigurable<MakeABConfig>();
            BuildTarget platform = config.m_BuildTarget;
            // (BuildTarget)EditorPrefs.GetInt("MakeAB.platform", (int)GetCurrentPlatform());
            if (config.m_BuildTarget == BuildTarget.NoTarget)
                platform = GetCurrentPlatform();
            EditorPrefs.SetString("Build.dest", output);
            var scenes = new List<string>();
            if (!scriptOnly)
            {
                var targets = EditorBuildSettings.scenes;
                var num = targets.Length;
                for (int i = 0; i < num; i++)
                {
                    var s = targets[i];
                    if (s.enabled)
                        scenes.Add(s.path);
                }

                for (int i = scenes.Count - 1; i >= 0; i--)
                {
                    var node = MakeABNode.FindNode(abnode, scenes[i]);
                    if (node != null && node.asset != null && node.asset.IsIncludeAsset(node.folder, scenes[i], null))
                        scenes.RemoveAt(i);
                }
            }
            //var back = output + ".bak";
            //Directory.Move(output, back);
            BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
            buildPlayerOptions.scenes = scenes.ToArray();
            buildPlayerOptions.target = platform;
            switch (platform)
            {
                case BuildTarget.StandaloneWindows:
                case BuildTarget.StandaloneWindows64:
                    buildPlayerOptions.locationPathName = NativeFileSystem.Combine(output, Application.productName + ".exe");
                    break;
                default:
                    buildPlayerOptions.locationPathName = output;
                    break;
            }
            BuildOptions opt = 0;
            if (development)
                opt = BuildOptions.Development;
            if (scriptOnly)
                opt |= BuildOptions.BuildScriptsOnly;
            buildPlayerOptions.options = opt;
            BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
            BuildSummary summary = report.summary;
            Debug.LogFormat("Build Player complete. ({0})", summary.result);
        }


        //public static string GetDestinationFolder()
        //{
        //    var platform = (BuildTarget)EditorPrefs.GetInt("MakeAB.platform", (int)GetCurrentPlatform());
        //    if (platform == BuildTarget.NoTarget)
        //        platform = GetCurrentPlatform();
        //    var dest = EditorPrefs.GetString("MakeAB.output", "Assets/StreamingAssets");// Path.Combine(Application.dataPath, "StreamingAssets", targetPlatform.ToString());
        //    if (!StringUtil.EndWithIgnoreCase(dest, platform.ToString()))
        //        dest = Path.Combine(dest, platform.ToString());
        //    return dest;
        //}

        static void BuildAssetBundles(bool searchAssets)
        {
            var cmd = new BuildAB(searchAssets, ProjectConfiguration.GetConfigurable<MakeABConfig>());
            ParallelUtils.Schedule(cmd, true);

        }

        struct AssetInfo
        {
            public int id;
            public int abid;
            public string path;
        }

        internal class ABMakeInfo : IInnerAssetBundle, ICommandHandler
        {

            readonly object _lock = new object();
            Dictionary<int, ABData> abmap;

            AvlTree<AssetInfo> mVariants;
            ParallelDispatcher.Handler mInitializer;
            AssetBundleVariant mKeywords;
            public bool IsActive { get; private set; }
            ParallelDispatcher.Handler ICommandHandler.GetHandler()
            {
                return mInitializer;
            }

            public void Activite()
            {
                lock (_lock)
                {
                    if (IsActive)
                        return;
                    IsActive = true;
                    System.Action exec = () =>
                    {
                        var config = ProjectConfiguration.GetConfigurable<MakeABConfig>();
                        if (!config.m_CacheAbList || !ReadAbmap(ref abmap))
                        {
                            var makenode = MakeAB.ParseAssetsTree(false);
                            if (makenode != null)
                                abmap = ParseABmap(config, makenode, false, true);
                        }
                    };
                    Debug.Log($"Activite MakeAB Info({GetHashCode()}).");
                    mKeywords = AssetsUtil.UtilInstance?.Variants;
#if UNITY_EDITOR
                    if (mKeywords == null)
                        mKeywords = AssetsUtil.VariantsForEditor;
#endif
                    if (mKeywords != null)
                    {
                        mKeywords.OnWillChangeVariants += OnVariantChanged;
                    }
                    var initKeywords = ParallelUtils.Schedule(() => mKeywords?.Initialize(), false);
                    //mKeywords.OnVariantWillChange += OnVariantChanged;
                    var prev = ParallelUtils.Combine(exec.Schedule(true), initKeywords);
                    mInitializer = ParallelUtils.Schedule(InitVariants, false, prev);
                    mInitializer.SampleExecuteTime("MakeAB");
                }
            }

            public void Deactivite()
            {
                lock (_lock)
                {
                    if (IsActive)
                    {
                        IsActive = false;
                        mInitializer.Abort();
                        if(mKeywords != null)
                            mKeywords.OnWillChangeVariants -= OnVariantChanged;
                        Debug.Log($"Deactivite MakeAB Info({GetHashCode()}).");
                    }
                }
            }

            private void OnVariantChanged(SliceList<IAssetVariantGroup> obj)
            {
                InitVariants();
            }

            void InitVariants()
            {
                mVariants = new AvlTree<AssetInfo>((a) => a.id);
                if (abmap != null)
                {
                    //var current = ABVariant.GetCurrentVariant();

                    //var alias = ABVariant.GetAlias(ABVariant.CurrentAlias);
                    HashSet<ABData> notIdentified = new HashSet<ABData>();
                    HashSet<string> variantsGrp = new HashSet<string>();
                    foreach (var ab in abmap.Values)
                    {
                        string abname, variant;
                        var group = mKeywords.GetABInfo(ab.abName, out abname, out variant);
                        if (group == null)
                            continue;
                        if (group.ActiveVariant.EqualsIgnoreCase(variant))
                        {
                            AssetInfo asset = new AssetInfo();
                            asset.abid = ParallelUtils.IgnoreCaseToHash(ab.abName);
                            variantsGrp.Add(abname);
                            foreach (var path in ab.assets)
                            {
                                asset.id = group.GetAssetAddress(path).IgnoreCaseToHash();
                                asset.path = path;
                                mVariants.Add(asset);
                            }
                        }
                        else /*if (variant.EqualsIgnoreCase(alias.Variant))*/
                        {
                            notIdentified.Add(ab);
                        }
                        //var isVariant = ABVariant.IsVariantAB(ab.abName);
                        //if (!isVariant)
                        //    continue;
                        //var n = ab.abName.LastIndexOf('.');
                        //var variant = ab.abName.Substring(n + 1);
                        //if (!StringUtil.EqualIgnoreCase(variant, current))
                        //    continue;
                        //if (variant.EqualsIgnoreCase(alias.Name))
                        //{
                        //    variantsGrp.Add(ab.abName.Substring(0, n));
                        //    var asset = new AssetInfo();
                        //    asset.abid = ParallelUtils.IgnoreCaseToHash(ab.abName);
                        //    var pattern = ABVariant.GetVariantAssetReplacePattern(variant);
                        //    foreach (var path in ab.assets)
                        //    {
                        //        asset.id = ParallelUtils.IgnoreCaseToHash(pattern.Replace(path, ABVariant.VariantAssetEvaluator));
                        //        asset.path = path;
                        //        mVariants.Add(asset);
                        //    }
                        //}
                        //else if(variant.EqualsIgnoreCase(alias.Variant))
                        //{
                        //    notIdentified.Add(ab);
                        //}
                    }
                    foreach (var ab in notIdentified)
                    {
                        string abname, variant;
                        var group = mKeywords.GetABInfo(ab.abName, out abname, out variant);
                        //var n = ab.abName.LastIndexOf('.');
                        //var abname = ab.abName.Substring(0, n);
                        if(!variantsGrp.Contains(abname))
                        {
                            //var variant = ab.abName.Substring(n + 1);
                            var asset = new AssetInfo();
                            asset.abid = ParallelUtils.IgnoreCaseToHash(ab.abName);
                            //var pattern = ABVariant.GetVariantAssetReplacePattern(variant);
                            foreach (var path in ab.assets)
                            {
                                asset.id = ParallelUtils.IgnoreCaseToHash(group.GetAssetAddress(path));// pattern.Replace(path, ABVariant.VariantAssetEvaluator));
                                asset.path = path;
                                mVariants.Add(asset);
                            }
                        }
                    }
                }
            }

            public bool HasAssetBundle(string abName)
            {
                var abid = ParallelUtils.IgnoreCaseToHash(abName);
                return abmap != null && abmap.ContainsKey(abid);
            }

            public string[] GetAllAssets(string abName)
            {
                if (abmap == null)
                    return null;
                //var current = ABVariant.GetCurrentVariant();
                var keywords = mKeywords;
                ABData ab;
                var abid = ParallelUtils.IgnoreCaseToHash(abName);
                //bool variant = false;
                IAssetVariantGroup variantGroup = null;
                if (!abmap.TryGetValue(abid, out ab) && keywords != null)
                {
                    for (int i = keywords.GroupCount - 1; ab == null && i >= 0; i--)
                    {
                        var grp = keywords.GetGroup(i);
                        var variantab = ParallelUtils.Concat(abName, '.', grp.ActiveVariant);
                        abid = variantab.IgnoreCaseToHash();
                        if (!abmap.TryGetValue(abid, out ab))
                            ab = null;
                        else
                            variantGroup = grp;
                    }
                }
                if (ab != null)
                {
                    var assets = new string[ab.assets.Count];
                    ab.assets.CopyTo(assets);
                    if (variantGroup != null)
                    {
                        //var pattern = ABVariant.GetVariantAssetReplacePattern(current);
                        for (int i = 0; i < assets.Length; i++)
                        {
                            assets[i] = variantGroup.GetAssetAddress(assets[i]);// pattern.Replace(assets[i], ABVariant.VariantAssetEvaluator);
                        }
                    }
                    return assets;
                }

                return null;
            }

            public string GetVariantAsset(string asset)
            {
                if (mInitializer.IsAlive)
                {
                    Debug.LogErrorFormat("Variants not initialized complete while get asset \"{0}\"", asset);
                    return asset;
                }
                var id = ParallelUtils.IgnoreCaseToHash(asset);
                var data = mVariants == null ? default : mVariants.GetData(id);
                if (data.id == id)
                {
                    return data.path;
                }
                else
                {
                    return asset;
                }
            }
        }


    }
}